%% Author: Administrator
%% Created: 2009-4-3
%% Description: Interface to GDAL 
-module(gdal).

%%
%% Include files
%%
-include_lib("eunit/include/eunit.hrl").

%% Opcodes
-define(OPEN,           $o).
-define(OPEN_WITH_IMG,  $m).
-define(CALC_HISTOGRAM, $h).
-define(CLOSE,          $c).
-define(WRITE,          $w).
-define(SPLIT_LIGHT,    $s).
-define(TO_PNG,         $p).
-define(GET_PNG,        $g).

%% ret codes
-define(VALUE,  $v).
-define(OK,     $o).
-define(FERROR, $E).
-define(REOF,   $x).
-define(BANDS,  $S).
-define(PNG,    $P).
-define(HISTOGRAM, $H).

-define(int32(X), 
        [((X) bsr 24) band 16#ff, ((X) bsr 16) band 16#ff,
         ((X) bsr 8) band 16#ff, (X) band 16#ff]).

%%
%% Exported Functions
%%
-export([load_driver/0, open/2, gopen/2, close/1, split_light/2, as_png/1, calc_histogram/2, to_png/3]).
-export([cm_list/2,test_calc_hist_memory/2,test_splitlight_memory/1, test_topng_memory/1, fb/0, test_aspng_memory/2]). 
-export([decode_bands/2, encode_bands/2]).
cm_list([], []) ->
    ok;
cm_list([H1|Tail1], [H2|Tail2]) ->
    if (H1 =/= H2) ->
           io:format("~p not equal ~p~n", [H1,H2]);
       true ->
           ok
    end,
    cm_list(Tail1, Tail2).

fb() ->
    load_driver(),
    {ok, GDALPort} = gopen("fb", "aaa"),
    {ok, ImgBin} = file:read_file("01.jpg"),
    Res = split_light(GDALPort, ImgBin),
    close(GDALPort),
    Res.

start_log(LogFile) ->
    application:start(log4erl),
    log4erl:add_file_appender(file, {"../../logs", LogFile, {size, 10000000}, 2, "elog", info}),
    log4erl:change_format(file, "%j %T %l%n").

test_aspng_memory(GDALPort, N) ->
%    load_driver(),
%    {ok,ImgBin} = file:read_file("01.jpg"),
%    {ok, GDALPort} = open("01", ImgBin),
    gdal:as_png(GDALPort),
    aspng_loop(GDALPort, N).
aspng_loop(_,0) ->
    aspng_over;
aspng_loop(GPort, N) ->
    {Time, {ok,_}} = timer:tc(gdal, as_png, [GPort]),
    if 
        (N rem 100) == 0 ->
            io:format("as_png:\t~p:\t~p~n", [N, Time]);
        true ->
            ok
    end,
%    log4erl:info("to_png: ~p\t~p\t~p", [N, now(), Time]),
    aspng_loop(GPort, N-1).
  
test_calc_hist_memory(GDALPort, N) ->
%    {ok,ImgBin} = file:read_file("01.jpg"),
%    {ok, GDALPort} = gdal:open("01", ImgBin),
    Bs = {red,green,blue},
    calc_loop(Bs, GDALPort, N).
calc_loop(_, _, 0) ->
    test_over;
calc_loop(Bs, GDALPort, N) ->
    BC = N rem 3 + 1,
    {T,{ok,_}}=timer:tc(gdal, calc_histogram, [GDALPort, element(BC, Bs)]),
    if
        (N rem 1000) == 0 ->
            io:format("calc_h:\t~p:\t~p~n", [N,T]);
        true ->
            ok
    end,
    calc_loop(Bs, GDALPort, N - 1).

test_splitlight_memory(N) ->
    start_log("splitlight"),
    {ok, GDALPort} = gopen("fb", "aaa"),
    {ok, ImgBin} = file:read_file("01.jpg"),
    spawn(fun() -> 
        split_loop(GDALPort, ImgBin, N, false) 
    end),
    {ok,Bin1} = file:read_file("band1.dat"),
    {ok,Bin2} = file:read_file("band2.dat"),
    {ok,Bin3} = file:read_file("band3.dat"),
    {ok, GPort} = gopen("split", "aaa"),
    Bands = [{256,256,Bin1},{256,256,Bin2},{256,256,Bin3}],
    spawn(fun() -> do_loop(GPort, Bands, N, false) end).

split_loop(_, _, 0, _) ->
    loop_over;
split_loop(GPort, ImgBin, N, Flag) ->
    {Time, {ok, _Bands}} = timer:tc(?MODULE, split_light,[GPort, ImgBin]),
    Now = time(), 
    {_, _, T} = Now,
    if 
        (((T rem 10) == 0) and Flag) ->
%        (N rem 60) == 0 ->
            NewFlag = false,
            log4erl:info("split: ~p\t~p\t~p", [N, Now, Time]);
        (T rem 10) =/= 0 ->
            NewFlag = true;
        true ->
            NewFlag = Flag
    end,
%    timer:sleep(3000),
    split_loop(GPort, ImgBin, N-1, NewFlag).

test_topng_memory(N) ->
    start_log("build_png"),
    %load_driver(),
    {ok, GPort} = gopen("", ""),
%    {ok, ImgBin} = file:read_file("01.jpg"),
%    {ok, Bands} = split_light(GPort, ImgBin),
    {ok,Bin1} = file:read_file("band1.dat"),
    {ok,Bin2} = file:read_file("band2.dat"),
    {ok,Bin3} = file:read_file("band3.dat"),
    Bands = [{256,256,Bin1},{256,256,Bin2},{256,256,Bin3}],
    do_loop(GPort, Bands, N, false).

do_loop(_, _, 0, _Flag) ->
    ok;
do_loop(GPort, Bands, N, Flag) ->
    {Time, _Value} = timer:tc(?MODULE, to_png, [GPort, Bands, integer_to_list(random:uniform(1000)) ++ ".png"]),
    Now = time(), 
    {_, _, T} = Now,
    if
        (((T rem 10) == 0) and Flag) ->
            NewFlag = false,
            log4erl:info("to_png: ~p\t~p\t~p", [N, Now, Time]);
        (T rem 10) =/= 0 ->
            NewFlag = true;
        true ->
            NewFlag = Flag
    end,
%    timer:sleep(3*1000),
    do_loop(GPort, Bands, N-1, NewFlag).

%%
%% API Functions
%%
load_driver() ->
    Dir = filename:join([filename:dirname(code:which(?MODULE)), "..", "priv"]),
    erl_ddll:load_driver(Dir, "GDAL_drv").

-spec open(CellId :: string(), ImgBin :: binary()) -> any().
open(CellId, ImgBin) ->
    {N1, N2, N3} = now(),
    ImgName = CellId ++ "_" ++ integer_to_list(N1) ++ "_" ++ integer_to_list(N2) ++ "_" ++ integer_to_list(N3),
    ImgNameBin = list_to_binary(ImgName),
    NameLen = length(ImgName),
    P = open_port({spawn, 'GDAL_drv'}, [binary]),
    Res = erlang_port_control(P, ?OPEN_WITH_IMG, <<NameLen, ImgNameBin/binary, ImgBin/binary>>),
    case decode(Res) of
    ok ->
        {ok, {gdal, P}};
    Err ->
        unlink(P),
        exit(P, die),
        Err
    end.

gopen(Fname, Flags) ->  
    P = open_port({spawn, 'GDAL_drv'}, [binary]),
    Res = erlang_port_control(P, ?OPEN, [Fname, 0, Flags, 0]),
    case decode(Res) of
    ok ->
        {ok, {gdal, P}};
    Err ->
        unlink(P),
        exit(P, die),
        Err
    end.

close({gdal, Fd}) ->
    unlink(Fd),
    catch erlang:port_close(Fd).

split_light({gdal, Port}, ImgBin) ->
    Res = erlang_port_control(Port, ?SPLIT_LIGHT, ImgBin),
    decode(Res)
.

-spec as_png({gdal, port()}) -> {ok, binary()}.
as_png({gdal, Port}) ->
    {N1, N2, N3} = now(),
    ImgName = "_" ++ integer_to_list(N1) ++ "_" ++ integer_to_list(N2) ++ "_" ++ integer_to_list(N3),
    ImgNameBin = list_to_binary(ImgName),
    NameLen = length(ImgName),
    Res = erlang_port_control(Port, ?GET_PNG, <<ImgNameBin/binary, 0>>),
    decode(Res).
%    Res.
 
-spec calc_histogram({gdal, port()}, red|green|blue) -> any().
calc_histogram({gdal, Port}, BandCode) ->
    Res = erlang_port_control(Port, ?CALC_HISTOGRAM, [band_code(BandCode)]),
    decode(Res).

band_code(red) ->
    1;
band_code(green) ->
    2;
band_code(blue) ->
    3.

to_png({gdal, Port}, Bands, FileName) ->
    BandsBin = encode_bands(Bands, <<>>),
    Len = length(FileName),
    File = list_to_binary(FileName),
    Res = erlang_port_control(Port, ?TO_PNG, <<Len, File/binary, BandsBin/binary>>),
    Res.
    %decode(Res).

%% Local Functions
%%
decode(Res)  ->
    case Res of
    <<?BANDS, BandsBin/binary>> ->
        Bands = decode_bands(BandsBin, []),
        {ok, Bands};
    <<?PNG, Bin/binary>> ->
        {ok, Bin};
    <<?HISTOGRAM, Bin/binary>> ->
        {ok, binary_to_intlist(Bin, [])};
    <<?VALUE, Bin/binary>> ->
        {ok, Bin};
    <<?OK>> ->
        ok;
    <<?FERROR, Err/binary>> ->
        {error, list_to_atom(binary_to_list(Err))};
    <<?REOF>> ->
        eof
    end.
binary_to_intlist(<<>>, Acc) ->
    lists:reverse(Acc);
binary_to_intlist(<<H:32,Rest/binary>>, Acc) ->
    binary_to_intlist(Rest, [H|Acc]).

decode_bands(<<>>, Bands) ->
    lists:reverse(Bands);
decode_bands(<<X1Size:32, Y1Size:32, Rest/binary>> = _BandsBin, Bands) ->
    BandLen = X1Size * Y1Size,
    <<Bin1:BandLen/binary, RestBin/binary>> = Rest,
    decode_bands(RestBin, [{X1Size, Y1Size, Bin1} | Bands]).
    
encode_bands([], ResBin) ->
    ResBin;
encode_bands([Band|RestBands], ResBin) ->
    {XSize, YSize, BandBin} = Band,
    encode_bands(RestBands, <<ResBin/binary, XSize:32, YSize:32, BandBin/binary>>).
    
erlang_port_control(P, C, Data) ->
    erlang:port_control(P, C, Data).

db_test() ->
    B1 = <<1:32, 1:32, 37>>,
    R1 = decode_bands(B1, []),
    ?assertEqual([{1,1,<<37>>}], R1),
    ?assertEqual(B1, encode_bands(R1, <<>>)).